package com.raos.zkconfig.handwritten.initializer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.raos.zkconfig.handwritten.beanpostprocessor.ParseRaosRefreshScopeBeanPostProcessor;
import com.raos.zkconfig.handwritten.refresh.FieldDetail;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.lang.reflect.Field;
import java.util.Map;

/**
 * 手写ZK配置中心之 zk配置 应用上下文初始器
 *
 * @author raos
 * @emil 991207823@qq.com
 * @date 2022/9/5 22:29
 */
public class ZkConfigApplicationContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        System.out.println("启动Spring Boot, 获取zk上的配置信息...");

        String connectStr = "192.168.235.129:2181";
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectionTimeoutMs(20000)
                .connectString(connectStr)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        curatorFramework.start();
        try {
            // 可以进行配置到 bootstrap.properties（启动后最先加载属性内容都在 bootstrap.properties）
            byte[] bytes = curatorFramework.getData().forPath("/raos-config/product-service");
            // String字符串的Json转成Map
            Map<String, Object> map = new ObjectMapper().readValue(new String(bytes), Map.class);
            System.out.println("从zookeeper server获取到的值为: " + map);

            // 将map保存到env中的PropertySource

            // 将map转换成MapPropertySource
            MapPropertySource mapPropertySource = new MapPropertySource("product-service-remote-env", map);

            ConfigurableEnvironment environment = context.getEnvironment();
            environment.getPropertySources().addFirst(mapPropertySource);
            System.out.println("env新增MapPropertySource成功.");

            // 设置永久的监听（用于更新属性）
            CuratorCache curatorCache = CuratorCache.build(curatorFramework, "/raos-config/product-service",
                    CuratorCache.Options.SINGLE_NODE_CACHE);
            CuratorCacheListener listener = CuratorCacheListener.builder().forAll(new CuratorCacheListener() {
                // 一旦对应 /raos-config/product-service ZNode发生变化，就会回调这个方法
                /**
                 * 数据获取的推拉模式
                 */
                @Override
                public void event(Type type, ChildData oldData, ChildData data) {
                    if (type.equals(Type.NODE_CHANGED)) {
                        // 知道了配置中心的数据更新了，接下来要获取最新的数据内容: 拉模式  推模式
                        System.out.println("ZNode数据更新了, 事件类型为: " + type);

                        // 客户端再从server拉取数据 curatorFramework.getData().forPath("/raos-config/product-service")
                        try {
                            Map<String, Object> updateMap = new ObjectMapper().readValue(new String(data.getData()), Map.class);
                            System.out.println("更新后的数据map为: " + updateMap);

                            environment.getPropertySources().replace("product-service-remote-env",
                                    new MapPropertySource("product-service-remote-env", updateMap));

                            // 获取到有哪些字段可能需要更新
                            ParseRaosRefreshScopeBeanPostProcessor parseRaosRefreshScopeBeanPostProcessor =
                                    context.getBean("parseRaosRefreshScopeBeanPostProcessor",
                                            ParseRaosRefreshScopeBeanPostProcessor.class);

                            Map<String, FieldDetail> fieldDetailMap = parseRaosRefreshScopeBeanPostProcessor.getFieldDetailMap();

                            for (String key : fieldDetailMap.keySet()) {
                                if (updateMap.containsKey(key)) {   // 判断远端发送过来的map数据中的key
                                    String value = environment.getProperty(key);
                                    Field field = fieldDetailMap.get(key).getField();
                                    field.setAccessible(true);
                                    // 反射更新字段的值    相当于AutowiredAnnotationBeanPostProcessor#AutowiredFieldElement#inject
                                    field.set(fieldDetailMap.get(key).getInstance(), value);
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).build();
            curatorCache.listenable().addListener(listener);
            curatorCache.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}










